#include <stm32f4xx.h>
#include <stm32f4xx_dcmi.h>
#include <stm32f4xx_dma.h>
#include <stm32f4xx_rcc.h>

#include <stm32f4xx_it.h>
#include "dcmi_hal.h"

/*
 +------------------------+-----------------------+----------------------------+
 +                       DCMI pins assignment                                  +
 +------------------------+-----------------------+----------------------------+
 |  DCMI_D0 <-> PC.6  |  DCMI_D6        <-> PE.5  |  SDIOC <-> PB.8 (SCL)
 |  DCMI_D1 <-> PC.7  |  DCMI_D7        <-> PE.6  |  SDIOD <-> PB.9 (SDA)
 |  DCMI_D2 <-> PC.8  |  DCMI_VSYNC     <-> PB.7
 |  DCMI_D3 <-> PC.9  |  DCMI_HSYNC     <-> PA.4
 |  DCMI_D4 <-> PE.4  |  DCMI_PIXCLK    <-> PA.6
 |  DCMI_D5 <-> PB.6  |  XCLK           <-> PA.8
 -------------------------------------------------------------------------------*/

#define DCMI_DR_ADDRESS       0x50050028

static uint8_t frame_buffer[IMAGE_SIZE];
static uint8_t frame_flag = 0;

static void dcmi_xclk_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_ClockSecuritySystemCmd(ENABLE);

    /* Enable GPIOs clocks */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_MCO);

    /* Configure MCO (PA8) */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* PA.8 at 180/5 = 36 Mhz*/
    RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_5);
}

static void dcmi_init(void)
{
    DCMI_InitTypeDef DCMI_InitStructure;
    DMA_InitTypeDef  DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    /* Enable Clock */
    RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

    /* B7: VSYNC*/
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_DCMI);
    /* A4: HSYNC*/
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_DCMI);
    /* A6: PCLK*/
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_DCMI);
    /* C6: data0*/
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_DCMI);
    /* C7: data1*/
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_DCMI);
    /* C8: data2*/
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_DCMI);
    /* C9: data3*/
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_DCMI);
    /* E4: data4*/
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource4, GPIO_AF_DCMI);
    /* B6: data5*/
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_DCMI);
    /* E5: data6*/
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource5, GPIO_AF_DCMI);
    /* E6: data7*/
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource6, GPIO_AF_DCMI);

    /* PA.4 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* PA.6 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* PB.6/7 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* PC.6/7/8/9 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    /* PE.5/6 */
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
    GPIO_Init(GPIOE, &GPIO_InitStructure);


    /* DCMI configuration */
    DCMI_InitStructure.DCMI_CaptureMode = DCMI_CaptureMode_SnapShot;
    //DCMI_CaptureMode_SnapShot;//DCMI_CaptureMode_Continuous;
    DCMI_InitStructure.DCMI_ExtendedDataMode = DCMI_ExtendedDataMode_8b;
    DCMI_InitStructure.DCMI_CaptureRate = DCMI_CaptureRate_All_Frame;
    DCMI_InitStructure.DCMI_VSPolarity = DCMI_VSPolarity_High;
    DCMI_InitStructure.DCMI_HSPolarity = DCMI_HSPolarity_Low;
    DCMI_InitStructure.DCMI_PCKPolarity = DCMI_PCKPolarity_Rising;
    DCMI_InitStructure.DCMI_SynchroMode = DCMI_SynchroMode_Hardware;
    DCMI_Init(&DCMI_InitStructure);
    //DCMI_JPEGCmd(ENABLE);

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    /* DMA2 Stream1 Configuration */
    DMA_DeInit(DMA2_Stream1);
    DMA_InitStructure.DMA_Channel = DMA_Channel_1;
    DMA_InitStructure.DMA_PeripheralBaseAddr =  (uint32_t)(DCMI_DR_ADDRESS);
    DMA_InitStructure.DMA_Memory0BaseAddr =  (uint32_t)frame_buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_InitStructure.DMA_BufferSize = sizeof(frame_buffer);///sizeof(uint8_t);//4;//0x9600 = Image size /4
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA_Mode_Circular;//DCMI_CaptureMode_SnapShot;//// First take one picture
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA2_Stream1, &DMA_InitStructure);

#ifdef HAS_DCMI_INTERRUPT
    /* DMA2 IRQ channel Configuration */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    DCMI_ITConfig(DCMI_IT_VSYNC, ENABLE);
    DCMI_ITConfig(DCMI_IT_LINE, ENABLE);
    DCMI_ITConfig(DCMI_IT_FRAME, ENABLE);
    DCMI_ITConfig(DCMI_IT_OVF, ENABLE);
    DCMI_ITConfig(DCMI_IT_ERR, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    DMA_ITConfig(DMA2_Stream1, DMA_IT_TC, ENABLE);
    DMA_ITConfig(DMA2_Stream1, DMA_IT_TE, ENABLE);
#endif

    DMA_Cmd(DMA2_Stream1, ENABLE);
    DCMI_Cmd(ENABLE);
}

#ifdef HAS_DCMI_INTERRUPT
void DMA2_Stream1_IRQHandler(void)
{
    /* DMA2 Channel1 Transfer Complete */
    if(DMA_GetITStatus(DMA2_Stream1,DMA_IT_TCIF1) ==  SET) {
        frame_flag = 1;
        DMA_ClearITPendingBit(DMA2_Stream1,DMA_IT_TCIF1);
    }
    if(DMA_GetITStatus(DMA2_Stream1,DMA_IT_TEIF1) ==  SET) {
        DMA_ClearITPendingBit(DMA2_Stream1,DMA_IT_TEIF1);
        while(1){}
    }
}

/* DCMI interrupt handler */
void DCMI_IRQHandler(void)
{
    if(DCMI_GetFlagStatus(DCMI_FLAG_FRAMERI) == SET) {
        DCMI_ClearFlag(DCMI_FLAG_FRAMERI);
    }
    if(DCMI_GetFlagStatus(DCMI_FLAG_OVFRI) == SET) {
        DCMI_ClearFlag(DCMI_FLAG_OVFRI);
        while(1){}
    }
}
#endif

/* APIs */

void dcmi_start_capture(void)
{
    DCMI_CaptureCmd(ENABLE);
}

void dcmi_stop_capture(void)
{
    DCMI_CaptureCmd(DISABLE);
}

uint8_t dcmi_is_frame_ready(void)
{
    return frame_flag;
}

uint8_t *dcmi_get_pointer_to_frame(void)
{
    return &frame_buffer;
}

void dcmi_open(void)
{
    dcmi_xclk_init();
    dcmi_init();
}

void dcmi_close(void)
{
}